﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tycho.Runtime;
using System.Diagnostics;

namespace Tycho.Persistence {
    /*
     * This represents a proxy to an object that is persisted.
     * 
     * Since the object is persisted, it's only purpose for being
     * in memory is speed, and to that end, once the system is low
     * on memory the object can be safely discarded by the garbage
     * collector. The proxy uses WeakReference semantics to accomplish
     * this.
     * 
     * The proxy can be in one of 3 states:
     * 
     * 1. "volatile": The object in-memory is either new, or is different
     *    from the persisted version. Either way, the in memory object is
     *    considered to be the most up to date version of the object.
     * 2. "loaded": The object in memory is the same as the
     *    persisted object and exists in memory only for performance reasons.
     *    In this state the object can be safely garbage collected.
     * 3. "stored": The in-memory object has been garbage collected, but
     *    the same object exists in persistent storage. If needed it can be
     *    loaded from the persitent store.
     * 
     * There are a number of transitions between these states:
     * 
     * volatile -> loaded: after a call to StoreObject ()
     * 
     * loaded -> volatile: after the target of the proxy is accessed. Once
     *    the target has been accessed it _may_ be modified so the proxy
     *    changes into volatile mode.
     *    
     * loaded -> stored: The weak reference is dropped during a garbage
     *    collection sweep. See WeakReference for details on this.
     * 
     * stored -> volatile: When the object is accessed (see loaded -> volatile).
     * 
     * Note that there is no [stored -> loaded] transition: the object is only
     * loaded lazily due to a need to access the object. The proxy cannot determine
     * if the object is merely going to be read or that it's going to be modified,
     * so it assumes the worst.
     * 
     * Note also that there is no [volatile -> stored] transition: the proxy
     * does NOT want modified in-memory objects to be garbage collected!
     * To prevent this we maintain a strong reference (StongReference) to the object while in
     * the volatile state.
     */
    public class PersistentObject : AnyObject {
        WeakReference WeakReference;
        AnyObject StrongReference;
        Guid PersistenceId;

        public PersistentObject (AnyObject target) {
            PersistenceId = Guid.Empty;
            WeakReference = new WeakReference (target);
            StrongReference = target;
        }

        public PersistentObject (Guid id) {
            PersistenceId = id;
            WeakReference = new WeakReference (null);
            StrongReference = null;
        }

        public override AnyObject InvokeMethod (AnyObject self, AnyObject name, params AnyObject [] arguments) {
            return Target.InvokeMethod (self, name, arguments);
        }

        AnyObject Target {
            get {
                if (WeakReference.IsAlive) {
                    // assign the strong reference - we're expecting the
                    // target to change (because it's being dereferenced)
                    // it's now "volatile", so we need to keep a strong ref
                    // to it.
                    StrongReference = (AnyObject) WeakReference.Target;
                    return StrongReference;
                } else {
                    LoadObject ();
                    return (AnyObject) StrongReference;
                }
            }
        }

        void LoadObject () {
            IPersister persister = PersisterFactory.Get ();
            Debug.Assert (PersistenceId != Guid.Empty);
            StrongReference = persister.LoadObject (PersistenceId);
            WeakReference.Target = StrongReference;
        }

        void StoreObject () {
            if (StrongReference != null) {
                if (PersistenceId == Guid.Empty) {
                    PersistenceId = Guid.NewGuid ();
                }

                IPersister persister = PersisterFactory.Get ();
                persister.StoreObject (StrongReference, PersistenceId);

                // now it's persisted we can remove the strong reference
                // and handle garbage collection on the object.
                StrongReference = null;
            } else {
                // no strong reference indicates that we've already persisted this
                // object, and no changes have been made to it since.
            }
        }
    }
}
